package com.zshy.base.kit.enums;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Maps;
import com.zshy.base.kit.cache.WeakCacheKit;
import com.zshy.base.kit.functions.FunctionKit;
import lombok.experimental.UtilityClass;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.lang.Nullable;

import java.util.*;
import java.util.function.Function;

/**
 * EnumUtils
 * <p>
 * 枚举工具类
 *
 * @author zhouhengzhe
 * @see EnumValueResolver
 */
@UtilityClass
public class EnumKit {


    /**
     * 根据给定的value查询对应的枚举值（将value转为字符串后进行匹配）。
     *
     * @param enumType 枚举类型Class对象
     * @param code     待查询的枚举值标识
     * @param <E>      枚举类型，必须实现EnumValueResolver接口
     * @return 如果找到对应的枚举值，则返回该枚举值；否则返回null
     * @throws IllegalArgumentException 如果enumType为null，则抛出此异常
     */
    @Nullable
    public <E extends Enum<E> & EnumValueResolver<E, ?>> E getByStringCode(Class<E> enumType, Object code) {
        // 判断枚举类型是否为空
        if (enumType == null) {
            throw new IllegalArgumentException("enumType cannot be null");
        }
        // 判断待查询的枚举值标识是否为空
        if (isNullCode(code)) {
            return null;
        }
        // 将待查询的枚举值标识转为字符串
        String codeStr = code.toString();
        // 获取枚举类型的所有枚举值
        E[] values = enumType.getEnumConstants();
        // 遍历枚举值数组
        for (E e : values) {
            // 判断枚举值的字符串标识是否与待查询的字符串标识相等
            if (e.getStringCode().equals(codeStr)) {
                // 如果相等，则返回对应的枚举值
                return e;
            }
        }
        // 如果没有找到对应的枚举值，则返回null
        return null;
    }

    /**
     * 根据 code 查询枚举
     *
     * @param enumType 枚举类型
     * @param code     枚举值标识
     * @param function 枚举值对应的转换函数
     * @param <E>      枚举类型
     * @return 如果找到对应的枚举值，则返回该枚举值；否则返回null
     */
    @Nullable
    public <E extends Enum<E>> E getByCode(Class<E> enumType, Object code, Function<E, ?> function) {
        return getByCode(enumType, code, function, (E) null);
    }


    /**
     * 根据 code 查询枚举值
     *
     * @param enumType    枚举类型，不能为空
     * @param code        待查询的枚举值标识，如果为null或者空字符串，则返回默认值
     * @param function    解析函数，用于将枚举值转换为可比较的对象，不能为空
     * @param defaultCode 默认值，当未找到对应的枚举值时返回该值，可以为null
     * @param <E>         枚举类型
     * @return 如果找到对应的枚举值，则返回该枚举值；否则返回默认值
     */
    @Nullable
    public <E extends Enum<E>> E getByCode(Class<E> enumType, Object code, Function<E, ?> function, @Nullable E defaultCode) {
        // 判断枚举类型是否为空
        if (enumType == null) {
            throw new IllegalArgumentException("enumType cannot be null");
        }
        // 如果 code 为空，则返回默认值
        if (isNullCode(code)) {
            return defaultCode;
        }
        // 获取枚举类型的所有枚举值
        E[] codes = enumType.getEnumConstants();
        // 遍历枚举值数组
        for (E e : codes) {
            // 将枚举值通过解析函数转换为可比较的对象，并判断是否等于 code
            Object value = function.apply(e);
            // 判断转换后的对象是否等于 code，注意空值检查
            if (value != null && value.equals(code)) {
                // 如果找到匹配的枚举值，则返回该枚举值
                return e;
            }
        }
        // 如果没有找到匹配的枚举值，则返回默认值
        return defaultCode;
    }

    /**
     * 根据给定的枚举类型和枚举值标识查询对应的枚举值。
     *
     * @param enumType 枚举类型Class对象
     * @param code     枚举值标识
     * @param <E>      枚举类型，必须实现EnumValueResolver接口
     * @return 如果找到对应的枚举值，则返回该枚举值；否则返回null
     * @throws IllegalArgumentException 如果enumType为null，则抛出此异常
     */
    @Nullable
    public <E extends Enum<E> & EnumValueResolver<E, ?>> E getByCode(Class<E> enumType, Object code) {
        return getByCode(enumType, code, (E) null);
    }


    /**
     * 根据给定的枚举类型和枚举值标识查询对应的枚举值，如果未找到则返回默认值。
     *
     * @param enumType    枚举类型Class对象
     * @param code        枚举值标识
     * @param defaultCode 默认值
     * @param <E>         枚举类型，必须实现EnumValueResolver接口
     * @return 如果找到对应的枚举值，则返回该枚举值；否则返回默认值
     * @throws IllegalArgumentException 如果enumType为null，则抛出此异常
     */
    public <E extends Enum<E> & EnumValueResolver<E, ?>> E getByCode(Class<E> enumType, Object code, E defaultCode) {
        // 判断枚举类型是否为空
        if (enumType == null) {
            throw new IllegalArgumentException("enumType cannot be null");
        }
        if (isNullCode(code)) {
            return defaultCode;
        }
        E[] values = enumType.getEnumConstants();
        for (E e : values) {
            if (e.getCode().equals(code)) {
                return e;
            }
        }
        return defaultCode;
    }


    /**
     * 根据给定的枚举类型和枚举值标识，通过解析函数查询对应的枚举值，若未找到则抛出异常。
     *
     * @param enumType 枚举类型Class对象
     * @param code     枚举值标识
     * @param function 解析函数，用于将枚举值转换为可比较的对象
     * @param <E>      枚举类型
     * @return 对应的枚举值
     * @throws IllegalStateException 如果未找到对应的枚举值，则抛出此异常
     */
    public <E extends Enum<E>> E requiredByCode(Class<E> enumType, Object code, Function<E, ?> function) {
        E e = getByCode(enumType, code, function);
        if (Objects.isNull(e)) {
            throw new IllegalStateException(StrUtil.format("不支持的枚举[{}]值: {}", enumType.getSimpleName(), code));
        }
        return e;
    }

    /**
     * 根据枚举类型和枚举值标识获取枚举值，若未找到则抛出运行时异常。
     *
     * @param enumType 枚举类型Class对象
     * @param code     枚举值标识
     * @param <E>      枚举类型，必须实现EnumValueResolver接口
     * @return 对应的枚举值
     * @throws RuntimeException 如果未找到对应的枚举值，则抛出运行时异常
     */
    public <E extends Enum<E> & EnumValueResolver<E, ?>> E requiredByCode(Class<E> enumType, Object code) {
        E e = getByCode(enumType, code);
        if (e == null) {
            throw new RuntimeException(StrUtil.format("不支持的枚举[{}]值: {}", enumType.getSimpleName(), code));
        }
        return e;
    }


    /**
     * 根据给定的枚举类型、枚举值标识、解析函数和候选枚举值数组，查询符合要求的枚举值。
     * 枚举值必须为候选值之一，否则抛出异常。
     *
     * @param enumType   枚举类型Class对象
     * @param code       枚举值标识
     * @param function   解析函数，用于将枚举值转换为可比较的对象
     * @param candidates 候选枚举值数组
     * @param <E>        枚举类型
     * @return 符合要求的枚举值
     * @throws IllegalArgumentException 如果枚举类型或枚举值标识为空，或枚举值不在候选值中，则抛出此异常
     */
    public <E extends Enum<E>> E requiredByCode(Class<E> enumType, Object code, Function<E, ?> function, E... candidates) {
        E e = requiredByCode(enumType, code, function);
        return candidate(enumType, e, code, candidates);
    }


    /**
     * 根据value查询枚举（并且必须是候选值之一）
     *
     * @param enumType   枚举类型Class对象
     * @param code       枚举值标识
     * @param candidates 候选枚举值数组
     * @param <E>        枚举类型，必须实现EnumValueResolver接口
     * @return 如果找到对应的枚举值，则返回该枚举值；否则抛出IllegalArgumentException异常
     * @throws IllegalArgumentException 如果enumType为null，或者code不是枚举类型的有效值，或者code不在candidates中，则抛出此异常
     */
    public <E extends Enum<E> & EnumValueResolver<E, ?>> E requiredByCode(Class<E> enumType, Object code, E... candidates) {
        E e = requiredByCode(enumType, code);
        return candidate(enumType, e, code, candidates);
    }


    /**
     * 判断是否为空值
     *
     * @param code 待判断的对象
     * @return 如果待判断的对象为空或者为空白字符串，则返回true；否则返回false
     */
    private boolean isNullCode(Object code) {
        if (Objects.isNull(code)) {
            return true;
        }
        return code instanceof String && StrUtil.isBlank(code.toString());
    }

    /**
     * 从候选枚举值中查找匹配的枚举值。
     *
     * @param enumType   枚举类型
     * @param e          当前枚举值
     * @param code       枚举值标识
     * @param candidates 候选枚举值
     * @param <E>        枚举类型
     * @return 匹配的枚举值
     * @throws IllegalStateException 如果不支持的枚举值
     * @SafeVarargs 允许将可变数量的参数传递给方法
     */
    @SafeVarargs
    private <E extends Enum<E>> E candidate(Class<E> enumType, E e, Object code, E... candidates) {
        for (E candidate : candidates) {
            if (e == candidate) {
                return e;
            }
        }
        throw new IllegalStateException(StrUtil.format("不支持的枚举[{}]值: {}", enumType.getSimpleName(), code));
    }


    /**
     * 判断枚举值是否存在
     *
     * @param enumType 枚举类型
     * @param code     枚举值标识
     * @param <E>      枚举类型，必须实现EnumValueResolver接口
     * @param <V>      枚举值标识的类型
     * @return 如果枚举值存在，则返回true；否则返回false
     */
    public <E extends Enum<E> & EnumValueResolver<E, V>, V> boolean exists(Class<E> enumType, V code) {
        if (Objects.isNull(code)) {
            return false;
        }
        return Objects.nonNull(getByCode(enumType, code));
    }


    /**
     * 获取枚举的 codeMap
     *
     * @param enumType 枚举类型
     * @param <E>      枚举类型，必须实现 EnumValueResolver 接口
     * @param <V>      枚举值的类型
     * @return 返回一个不可修改的 Map，键为枚举值的字典值，值为枚举值的字典描述
     */
    public <E extends Enum<E> & EnumValueResolver<E, V>, V> Map<V, String> getCodeMap(Class<E> enumType) {
        E[] codes = enumType.getEnumConstants();
        Map<V, String> codeMap = Maps.newHashMapWithExpectedSize(codes.length);
        for (E code : codes) {
            codeMap.put(code.getCode(), code.getMessage());
        }
        return Collections.unmodifiableMap(codeMap);
    }


    /**
     * 获取枚举的 codeMap（会进行弱缓存）
     *
     * @param enumType 枚举类型
     * @param <E>      枚举类型，必须实现 EnumValueResolver 接口
     * @param <V>      枚举值对应的类型
     * @return <字典值, 字典描述> 的 Map 对象
     */
    public <E extends Enum<E> & EnumValueResolver<E, V>, V> Map<V, String> getCodeMapCache(Class<E> enumType) {
        return WeakCacheKit.weak().get(enumType.getName(), () -> getCodeMap(enumType));
    }


    /**
     * 获取枚举值描述
     *
     * @param enumType    枚举类型
     * @param code        枚举值
     * @param defaultCode 默认值
     * @return 枚举描述，如果未找到对应枚举值，则返回默认值
     * @throws NullPointerException 如果枚举类型为空，则抛出此异常
     */
    @Nullable
    public <E extends Enum<E> & EnumValueResolver<E, V>, V> String getEnumMessage(Class<E> enumType, V code, String defaultCode) {
        if (Objects.isNull(code)) {
            return defaultCode;
        }
        return getCodeMapCache(enumType).getOrDefault(code, defaultCode);
    }


    /**
     * 根据枚举类型和枚举值获取枚举值的描述信息。
     *
     * @param enumType 枚举类型
     * @param code     枚举值
     * @param <E>      枚举类型，必须实现EnumValueResolver接口
     * @param <V>      枚举值的类型
     * @return 枚举描述，如果未找到对应枚举值则返回null
     */
    @Nullable
    public <E extends Enum<E> & EnumValueResolver<E, V>, V> String getEnumMessage(Class<E> enumType, V code) {
        return getEnumMessage(enumType, code, null);
    }


    /**
     * 获取枚举值描述
     *
     * @param enumType 枚举类型
     * @param code     枚举值
     * @param <E>      枚举类型，必须实现EnumValueResolver接口
     * @param <V>      枚举值的类型
     * @return 枚举描述（不存在则返回原 value）
     */
    public <E extends Enum<E> & EnumValueResolver<E, V>, V> String getEnumCodeMessage(Class<E> enumType, V code) {
        String desc = getEnumMessage(enumType, code);
        if (Objects.nonNull(desc)) {
            return desc;
        }
        return FunctionKit.map(code, String::valueOf);
    }

    /**
     * 传入code集合，过滤掉非枚举值
     *
     * @param codes    枚举值集合
     * @param enumType 枚举类型
     * @param <E>      枚举类型，必须实现EnumValueResolver接口
     * @param <V>      枚举值类型
     * @return 过滤好的枚举值集合
     */
    public <E extends Enum<E> & EnumValueResolver<E, V>, V> Set<V> purifyEnumCode(Collection<V> codes, Class<E> enumType) {
        Set<V> res = new HashSet<>();
        if (CollectionUtils.isNotEmpty(codes)) {
            for (V code : codes) {
                if (Objects.nonNull(getEnumMessage(enumType, code))) {
                    res.add(code);
                }
            }
        }
        return res;
    }


    /**
     * 判断目标值是否是枚举值。
     *
     * @param code  待判断的枚举值
     * @param enums 枚举值解析器列表
     * @param <V>   枚举值的类型
     * @return 如果目标值是枚举值之一，则返回true；否则返回false
     * @throws IllegalArgumentException 如果枚举值解析器列表为空，则抛出此异常
     * @since 版本号
     */
    @SafeVarargs
    public <V> boolean isIn(V code, EnumValueResolver<?, V>... enums) {
        Assert.notEmpty(enums);
        return isIn(code, CollUtil.newHashSet(enums));
    }


    /**
     * 判断目标值是否是枚举值
     *
     * @param code    目标值
     * @param enumSet 枚举值集合
     * @param <V>     目标值的类型
     * @return 如果目标值是枚举值，则返回true；否则返回false
     * @throws IllegalArgumentException 如果枚举值集合为空，则抛出此异常
     */
    public <V> boolean isIn(V code, Set<? extends EnumValueResolver<?, V>> enumSet) {
        Assert.notEmpty(enumSet);
        if (Objects.isNull(code)) {
            return false;
        }

        for (EnumValueResolver<?, V> anEnum : enumSet) {
            if (anEnum.isEquals(code)) {
                return true;
            }
        }
        return false;
    }

}
